﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Net.Sockets;
using System.Threading;
using System.Collections;
using System.Net;

namespace ServidorQuienEsQuien
{
 

    /// <summary>
    /// Servidor, se encargará de intermediar entre los clientes y mandar los mensajes a todos
    /// </summary>
    public partial class ServerWindow : Window
    {
        /*Variables*/
        private TcpListener tcpListener = null; //Socket a la espera de punto remoto 
        private TcpClient tcp = null; //Socket de comunicación para el cliente 
        private byte[] dataToSend = null; //Datos a enviar 
        private Thread hiloserver = null; //Hilo para el servidor 
        public ArrayList conexiones; //Colección de conexiones creada 
        int puertoBase; //Puerto al que conectarse 
        string ipLocal; //IP Local
        public int nJugadores = 0;

        private ArrayList listaJugadoresChat = new ArrayList();
        private ArrayList listaJugadoresAdresses = new ArrayList();
        /*Constantes codificadas*/
        /// <summary>
        /// Mensaje codificado de que entra un jugador al chat
        /// </summary>
        private String unirseAlChat = "/JoinPlayer";
        /// <summary>
        /// Mensaje codificado de que sale un jugador del chat
        /// </summary>
        private String quitarChat = "/QuitPlayer";
        /// <summary>
        /// Mensaje codificado de que se hace un duelo
        /// </summary>
        private String retarJugador = "/DuelPlayer";
        /// <summary>
        /// Mensaje codificado de que acepta un duelo
        /// </summary>
        private String aceptarReto = "/AcceptDuel";
        /// <summary>
        /// Mensaje codificado de que se rechaza un duelo
        /// </summary>
        private String negarReto = "/DenieDuel";
        /// <summary>
        /// Mensaje codificado de que un jugador a sido retado pero este ya esta jugando
        /// </summary>
        private String estoyJugando = "/AlreadyPlaying";
        /*Conexiones auxiliares*/
        TcpListener aux = null;

        /// <summary>
        /// Constructor del servidor vacio
        /// </summary>
        public ServerWindow()
        {
            InitializeComponent();
            this.ipBox.Text = this.LocalIPAddress();
            ipLocal = ipBox.Text;
            conexiones = new ArrayList();
    
        }

        /// <summary>
        /// Evento que se produce cuando se cierra la ventana, se cierra todo
        /// </summary>
        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            Environment.Exit(0);//Cerramos la ventana
        }


        /*Metodo para capturar la Ip*/
        private string LocalIPAddress()
        {
            IPHostEntry host;
            string localIP = "";
            host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (IPAddress ip in host.AddressList)
            {
                if (ip.AddressFamily.ToString() == "InterNetwork")
                {
                    //Mediante este if descartamos las Ips erroneas
                    /*if (ip.ToString().Substring(0, 3) != "192")
                    {
                        localIP = ip.ToString();
                    }*/
                    //Descomentar en casa y comentar el If, ya que puede dar problemas
                    localIP = ip.ToString();
                }
            }
            return localIP;
        }


        /// <summary>
        /// Clase con algunas constantes que se van a usar
        /// </summary>
        public class Constants
        {
            public const int noOfBytesToReadForSuperPacket = 4;
            public const int maxNoOfBytes = 784;
        }

        /// <summary>
        /// Evento que se produce al hacer click en el botón conectar
        /// </summary>
        private void conectarBtn_Click(object sender, RoutedEventArgs e)
        {
            this.Title = "QuienesQuien Server";
            // Obtenemos el localhost
            IPHostEntry localHost = Dns.GetHostEntry(Dns.GetHostName());
            // Creamos el EndPoint
            IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Parse(LocalIPAddress()),
            int.Parse(this.puertoBox.Text));
            // Instanciamos el canal TCP a la escucha a parte con lo que no se queda pillada la interfaz
            tcpListener = new TcpListener(localEndPoint);
            tcpListener.Start();
            //Esperamos clientes en un hilo a parte con lo que no se queda pillado
            puertoBase = int.Parse(this.puertoBox.Text);
            hiloserver = new Thread(this.EscuchaInfinitaNuevos);
            /*Inicializamos el servidor....*/
            this.infoServerTxt.AppendText("\n");
            this.infoServerTxt.AppendText("Servidor ON");
            this.conectarBtn.Visibility = Visibility.Hidden;//Ocultamos el boton
            /**/
            hiloserver.Start();

        }


        /// <summary>
        /// Método que escucha nuevos clientes de manera infinita e interrumpida
        /// </summary>
        private void EscuchaInfinitaNuevos()
        {
            for (; ; )
            {
                Thread.Sleep(100);
                // Si existe alguna petición se acepta y se crea un objeto con la conexión
                if (tcpListener.Pending())
                {
                    try
                    {
                        int port = puertoBase + (nJugadores * 2) + 2;
                        tcp = tcpListener.AcceptTcpClient();
                        enviarMensaje(tcp, port.ToString());//Aqui le dice al cliente que use otro puerto
                        tcp.Close(); //Cerramos y creamos nueva conexión en el nuevo puerto
                        //TcpListener aux = new TcpListener(IPAddress.Parse(LocalIPAddress()), port);
                        aux = new TcpListener(IPAddress.Parse(LocalIPAddress()), port);
                        aux.Start();
                        while (!aux.Pending()) Thread.Sleep(100);
                        TcpClient auxtcp = aux.AcceptTcpClient();
                        conexiones.Add(new Conexion(this, auxtcp));
                        nJugadores++;
                        aux.Stop();//Paramos el tcpListener
                    }
                    catch (Exception e) { MessageBox.Show(e.ToString()); }
                }
            }
        }

        /// <summary>
        /// Método que manda un mensaje de String a Bytes a una conexión determinada
        /// </summary>
        private void enviarMensaje(TcpClient tc, string msg)
        {
            //Prepara mensaje para enviar como array de bytes
            char[] charArray = msg.ToCharArray(0,
            msg.Length);
            dataToSend = new byte[msg.Length];
            for (int charCount = 0; charCount < msg.Length; charCount++)
            {
                dataToSend[charCount] = (byte)charArray[charCount];
            }
            //Usamos el socket para enviar el mensaje
            tc.GetStream().Write(dataToSend, 0, dataToSend.Length);
        }

        /// <summary>
        /// Delegado para escribir en la interfaz cuando un mensaje lleva al servidor
        /// </summary>
        /// <param name="msg">Mensaje a trazear</param>
        private delegate void RecibirMensajeDel(string msg);

        public void delegarRecibirMensaje(string msg)
        {
            RecibirMensajeDel aux = new RecibirMensajeDel(this.escribirMensaje);
            Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, aux, msg);
        }

        /// <summary>
        /// Este método parsea todos los mensajes que llegan al delegado y realiza la acción correspondiente a cada uno
        /// </summary>
        /// <param name="msg">Mensaje a tratar</param>
        private void escribirMensaje(string msg)
        { //Escribo en mi interfaz a través de la invocación del delegado (Cliente)

            /*Primero hay que parsear el mensaje (Codificacion o mensaje normal)*/

            //Un jugador se une al chat, entonces lo añadimos a la lista de jugadores
            if (msg.Contains(unirseAlChat))
            {
                //listaJugadoresChat.Remove(quitarChat);
                Boolean extrajoNombre = false;
                listaJugadoresChat.Remove(unirseAlChat);
                listaJugadoresChat.Insert(0, unirseAlChat); //Añadimos a la cabeza /UnirseAlChat

                nombreDirecciones nomDir = new nombreDirecciones();

                char[] delimitador = new char[] { ' ' };
                foreach (string substr in msg.Split(delimitador))
                {
                    if (!substr.Contains("/"))
                    {
                        if (!extrajoNombre)
                        {
                            nomDir.nombre = substr;
                            listaJugadoresChat.Add(substr);
                            extrajoNombre = true;
                        }
                        else
                        {
                            nomDir.direccion = substr;
                        }

                    }
                }
                listaJugadoresAdresses.Add(nomDir);

                //Descomponemos el ArrayList a un String
                String listaJugadoresString = componerListaJugadoresString(listaJugadoresChat);
                
                //Como se unio un jugador le mandamos la lista de jugadores actuales en formato String a todos.
                for (int i = 0; i < this.nJugadores; i++)
                {
                    Conexion auxcon = (Conexion)this.conexiones[i];
                    enviarMensaje(auxcon.getSocket(), listaJugadoresString);
                }
            }
            //Un judador se sale del chat, entoces lo quitamos de la lista de jugadores y cerramos su conexion
            else if (msg.Contains(quitarChat))
            {
                //listaJugadoresChat.Remove(quitarChat);
                Boolean encontrado = false;
                listaJugadoresChat.Remove(unirseAlChat);
                listaJugadoresChat.Insert(0, unirseAlChat);//Añadimos a la cabeza /QuitarChat
                char[] delimitador = new char[] { ' ' };
                foreach (string substr in msg.Split(delimitador))
                {
                    if (!substr.Contains("/"))
                    {
                        //Elimino de la lista de jugadores
                        listaJugadoresChat.Remove(substr);

                        //Tambien de la lista de conexiones
                        nombreDirecciones nomAuxIndice = new nombreDirecciones(substr);
                        int indice = listaJugadoresAdresses.IndexOf(nomAuxIndice);
                        nombreDirecciones nomAuxDireccion = (nombreDirecciones)listaJugadoresAdresses[indice];
                        
                        for (int i = 0; i < nJugadores && !encontrado; i++)//Busco la conexion y la elimino
                        {
                            //El RE del Cliente es el LE de la conexion en el servidor (Cuidado con eso)
                             Conexion auxcon = (Conexion)this.conexiones[i];
                             if (auxcon.getSocket().Client.LocalEndPoint.ToString() == nomAuxDireccion.direccion)
                             {

                                 auxcon.stopThread();//Paramos el hilo de ese objeto (para que no siga escuchando) Parte servidora
                                 conexiones.RemoveAt(i);
                                 encontrado = true;
                                 nJugadores--;
                             }
                        }
                        //Elimino de la lista de direcciones
                        nombreDirecciones nom = new nombreDirecciones(substr);
                        listaJugadoresAdresses.Remove(nom);
                        

                    }
                }
                //Descomponemos el ArrayList a un String
                String listaJugadoresString = componerListaJugadoresString(listaJugadoresChat);

                //Como se quito un jugador le mandamos la lista de jugadores actuales en formato String a todos.
                for (int i = 0; i < this.nJugadores; i++)
                {
                    Conexion auxcon = (Conexion)this.conexiones[i];
                    enviarMensaje(auxcon.getSocket(), listaJugadoresString);
                }
            }

            //Si recibe un reto se lo mandará al jugador apropiado, pero para ello primero debe buscar el socket del jugador retado
            else if (msg.Contains(retarJugador))
            {
                Boolean extrajoNombre = false;
                Boolean encontrado = false;
                String direccionEndPointOrigen = "";
                String direccionEndPointDestino = "";
                String nombreDestino = "";
                

                //msg de la forma: //Orden + NombreJugadorDestino + DireccionEndPoint
                char[] delimitador = new char[] { ' ' };
                foreach (string substr in msg.Split(delimitador))
                {
                    if (!substr.Contains("/"))
                    {
                        if (!extrajoNombre)
                        {
                            nombreDestino = substr;
                            extrajoNombre = true;
                        }
                        else
                        {
                            direccionEndPointOrigen = substr;
                        }
                    }
                }
                

                //1)Buscamos el EndPoint Destino
                nombreDirecciones nombreDirAux2 = new nombreDirecciones(nombreDestino);
                int indiceDestino = listaJugadoresAdresses.IndexOf(nombreDirAux2);
                nombreDirecciones nombreDirAux = (nombreDirecciones)listaJugadoresAdresses[indiceDestino];
                direccionEndPointDestino = nombreDirAux.direccion;

                //2)Obtenemos la conexion a traves del EndPointDestino
                for (int i = 0; i < nJugadores && !encontrado; i++)
                {
                    Conexion auxcon = (Conexion)this.conexiones[i];
                    //3)Si lo encontramos lanzamos el mensaje de reto
                    if (auxcon.getSocket().Client.LocalEndPoint.ToString() == direccionEndPointDestino)
                    {
                        //Orden +  DireccionEndPointOrigen
                        enviarMensaje(auxcon.getSocket(), retarJugador+" "+direccionEndPointOrigen);
                    }
                }
            }
            //Contiene mensaje de aceptar reto de un cliente.
            else if(msg.Contains(aceptarReto)){
                //Se lo comunicamos al otro cliente
                Boolean extrajoNombre = false;
                Boolean encontrado = false;
                String direccionEndPointOrigen = "";
                String direccionEndPointDestino = "";
                String nombreDestino = "";


                //msg de la forma: //Orden + NombreJugadorDestino + DireccionEndPointOrigen
                char[] delimitador = new char[] { ' ' };
                foreach (string substr in msg.Split(delimitador))
                {
                    if (!substr.Contains("/"))
                    {
                        if (!extrajoNombre)
                        {
                            nombreDestino = substr;
                            extrajoNombre = true;
                        }
                        else
                        {
                            direccionEndPointOrigen = substr;
                        }
                    }
                }
                //2)Obtenemos la conexion a traves del EndPointDestino
                for (int i = 0; i < nJugadores && !encontrado; i++)
                {
                    Conexion auxcon = (Conexion)this.conexiones[i];
                    //3)Si lo encontramos lanzamos el mensaje de reto
                    if (auxcon.getSocket().Client.LocalEndPoint.ToString() == direccionEndPointOrigen)
                    {
                        //Orden +  DireccionEndPointOrigen
                        enviarMensaje(auxcon.getSocket(), aceptarReto);
                        encontrado = true;
                        //Deberiamos abrir un hilo nuevo para la partida?pasandole los 2 EndPoints y hacer los movimientos ahi?
                    }
                }

            }

            //Contiene una denegacion de reto
            else if(msg.Contains(this.negarReto)){

                //Se lo comunicamos al otro cliente
                Boolean extrajoNombre = false;
                Boolean encontrado = false;
                String direccionEndPointOrigen = "";
                String direccionEndPointDestino = "";
                String nombreDestino = "";


                //msg de la forma: //Orden + NombreJugadorDestino + DireccionEndPointOrigen
                char[] delimitador = new char[] { ' ' };
                foreach (string substr in msg.Split(delimitador))
                {
                    if (!substr.Contains("/"))
                    {
                        if (!extrajoNombre)
                        {
                            nombreDestino = substr;
                            extrajoNombre = true;
                        }
                        else
                        {
                            direccionEndPointOrigen = substr;
                        }
                    }
                }
                //2)Obtenemos la conexion a traves del EndPointDestino
                for (int i = 0; i < nJugadores && !encontrado; i++)
                {
                    Conexion auxcon = (Conexion)this.conexiones[i];
                    //3)Si lo encontramos lanzamos el mensaje de reto
                    if (auxcon.getSocket().Client.LocalEndPoint.ToString() == direccionEndPointOrigen)
                    {
                        //Orden +  DireccionEndPointOrigen
                        enviarMensaje(auxcon.getSocket(), negarReto);
                        encontrado = true;
                       
                    }
                }
            }
            else if(msg.Contains(this.estoyJugando)){
                //Se lo comunicamos al otro cliente
                Boolean extrajoNombre = false;
                Boolean encontrado = false;
                String direccionEndPointOrigen = "";
                String direccionEndPointDestino = "";
                String nombreDestino = "";


                //msg de la forma: //Orden + NombreJugadorDestino + DireccionEndPointOrigen
                char[] delimitador = new char[] { ' ' };
                foreach (string substr in msg.Split(delimitador))
                {
                    if (!substr.Contains("/"))
                    {
                        if (!extrajoNombre)
                        {
                            nombreDestino = substr;
                            extrajoNombre = true;
                        }
                        else
                        {
                            direccionEndPointOrigen = substr;
                        }
                    }
                }
                //2)Obtenemos la conexion a traves del EndPointDestino
                for (int i = 0; i < nJugadores && !encontrado; i++)
                {
                    Conexion auxcon = (Conexion)this.conexiones[i];
                    //3)Si lo encontramos lanzamos el mensaje de reto
                    if (auxcon.getSocket().Client.LocalEndPoint.ToString() == direccionEndPointOrigen)
                    {
                        //Orden +  DireccionEndPointOrigen
                        enviarMensaje(auxcon.getSocket(), estoyJugando);
                        encontrado = true;
                        
                    }
                }
            }

            //Mensaje normal de texto
            else
            {
                //Mandamos el mensaje recibido al resto de los clientes
                for (int i = 0; i < this.nJugadores; i++)//Para cada una de las conexiones le mandamos un mensaje
                {//Recordar que este array lo tiene el servidor, el cliente solo tiene una conexion
                    Conexion auxcon = (Conexion)this.conexiones[i];
                    //Recupero el socket y le mando el mensaje a los demas clientes 
                    enviarMensaje(auxcon.getSocket(), msg);
                }
            }

        }
        /// <summary>
        /// Método que compone la lista de jugadores actuales en el salon Chat de forma que se pueda tratado posteriormente por los Clientes
        /// </summary>
        /// <param name="listaJugadoresChat">Lista de jugadores con todos los jugadores que va a ser parseada y modificada</param>
        private string componerListaJugadoresString(ArrayList listaJugadoresChat)
        {
            String listaJugadoresString = "";
            for (int i = 0; i < listaJugadoresChat.Count; i++)
            {
                listaJugadoresString += listaJugadoresChat[i]+"*";
            }
            return listaJugadoresString;
        }

        /// <summary>
        /// Método que recibe un array de Bytes y lo convierte en un String
        /// </summary>
        /// <returns>Mensaje de Bytes transformado en un String</returns>
        private string recibirMensaje()
        {
            int readBytes = 0;
            StringBuilder msg = new StringBuilder(Constants.maxNoOfBytes);
            byte[] byteNom = new byte[Constants.maxNoOfBytes];
            byte count1 = 0x01;
            for (int i = 0; i < byteNom.Length; i++)
                byteNom[i] = count1++;
            while (readBytes == 0)
            {
                readBytes = tcp.GetStream().Read(byteNom, 0, byteNom.Length);
                for (int i = 0; i < readBytes; i++)
                    msg.Append((char)byteNom[i]);
                Thread.Sleep(200);
            }
            string mensaje = msg.ToString();
            if (mensaje == null) mensaje = recibirMensaje();
            return mensaje;
        }


        /// <summary>
        /// Método para liberar recursos de los hilos - Deprecated
        /// </summary>
        private void liberarRecursos(object sender, RoutedEventArgs e)
        {
            if (hiloserver.IsAlive)
            {
                if (tcp.Connected)
                {
                    //Matamos los hilos y liberamos el boton
                    //this.enviarDesconexion(); 
                    if (tcp != null) tcp.GetStream().Close();
                    if (tcpListener != null) tcpListener.Stop();
                    if (tcp != null) tcp.Close();

                    this.conectarBtn.Visibility = Visibility.Visible;
                    this.infoServerTxt.AppendText("\n");
                    this.infoServerTxt.AppendText("Desconexión correcta");
                    this.infoServerTxt.AppendText("\n");
                    this.infoServerTxt.AppendText("Servidor OFF");
                }
            }
            else
            {
                this.infoServerTxt.AppendText("\n");
                this.infoServerTxt.AppendText("Desconexión incorrecta");
            }
        }
        //Representa la clave Nombre -> Direccion
        private class nombreDirecciones{
            public String nombre;
            public String direccion;

            public nombreDirecciones()
            {
            }
            public nombreDirecciones(String NuevoNombre)
            {
                nombre = NuevoNombre;
            }
            //2 objetos son iguales si estos tienen el mismo nombre (es como identificador unico)
            public override bool Equals(Object obj)
            {
                return nombre == ((nombreDirecciones)obj).nombre;
            }
        }

    }
}
